/*
 * Copyright (C) 2001-2006 the xine-project
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
 * USA
 *
 * $Id: main.c,v 1.163 2006/04/08 21:34:50 dsalt Exp $
 *
 * gtk2 ui for xine
 *
 * main
 */

#include "globals.h"
#include "version.h"

#include <string.h>
#include <signal.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <X11/Xlib.h>

#include <stdio.h>
#include <stdlib.h>

#ifdef HAVE_GETOPT_LONG
#include <getopt.h>
#endif

#include <gtk/gtk.h>
#include <gdk/gdk.h>
#include <gdk/gdkx.h>
#include <gdk/gdkkeysyms.h>
#include <glib.h>
#include <glib/gmessages.h>

#include "engine.h"
#include "player.h"
#include "gtkvideo.h"
#include "info_widgets.h"
#include "wizards.h"
#include "xml_widgets.h"
#include "noskin_window.h"
#include "server.h"
#include "open_mrl.h"
#include "log_window.h"
#include "preferences.h"
#include "key_events.h"
#include "mediamarks.h"
#include "playlist.h"
#include "settings.h"
#include "snapshot.h"
#include "play_item.h"
#include "lirc.h"
#include "stream_info.h"
#include "ui.h"
#include "utils.h"
#include "vis.h"
#include "post.h"
#include "console_output.h"

/*
 * globals
 */

int             verbosity;
GtkWidget      *app = NULL;
gchar          *plugindir, *bindir, *logodir, *pixmapdir, *icondir, *miscdir, *confdir;
gchar          *video_driver_id=NULL;
gchar          *audio_driver_id=NULL;

static void
gxine_try_remote (int argc, char *argv[], gboolean enqueue, gboolean autoplay)
{
  if (!server_client_connect())
    return;

  /*
   * pass on files to play
   */

  if (!enqueue)
    server_client_send ("playlist_clear();\n");

  if (optind < argc)
  {
    int i;
    for (i = optind; i < argc; ++i)
    {
      if (autoplay && i == optind)
        server_client_send ("playlist_play (");
      server_client_send ("playlist_add (\"");
      char *uri = make_path_uri (argv[i]);
      server_client_send (uri ? uri : argv[i]);
      server_client_send ((autoplay && i == optind) ? "\"));\n" : "\");\n");
      free (uri);
    }
  }

  exit (0);
}

static gboolean splash_destroy (gpointer splash)
{
  gtk_widget_destroy (splash);
  return FALSE;
}

static void splash_show (void)
{
  gchar     *pathname;
  GtkWidget *splash, *fixed, *img, *text;

  gdk_threads_enter();

  splash = gtk_window_new (GTK_WINDOW_TOPLEVEL);
  GtkWindow *w = GTK_WINDOW (splash);
  gtk_window_set_decorated (w, FALSE);
  gtk_window_set_type_hint (w, GDK_WINDOW_TYPE_HINT_SPLASHSCREEN);
  gtk_window_set_position (w, GTK_WIN_POS_CENTER_ALWAYS);
  gtk_window_set_keep_above (w, TRUE);
  gtk_window_stick (w);

  pathname = g_build_filename (pixmapdir, "splash.png", NULL);
  img = gtk_image_new_from_file (pathname);
  g_free (pathname);

  fixed = gtk_fixed_new ();
  gtk_container_add (GTK_CONTAINER (splash), fixed);
  gtk_fixed_put (GTK_FIXED (fixed), img, 0, 0);

  text = gtk_label_new ("<span font_desc='sans 12' color='#DDDDDD'>"VERSION"</span>");
  gtk_label_set_use_markup (GTK_LABEL (text), TRUE);
  gtk_misc_set_alignment (GTK_MISC (text), 1, 1);
  gtk_fixed_put (GTK_FIXED (fixed), text, 0, 0);

  gtk_widget_show_all (splash);
  /* the widgets need to be realised before we can move the label */
  gtk_fixed_move (GTK_FIXED (fixed), text,
		  img->allocation.width - text->allocation.width,
		  img->allocation.height - text->allocation.height);

  gtk_timeout_add (4000, splash_destroy, splash);

  do_pending_events ();
  gdk_threads_leave();
}

#ifndef GLIB_USES_SYSTEM_MALLOC
#include <assert.h>
gpointer xmalloc (gsize size)
{
  gpointer p = malloc (size);
  if (size)
    assert (p != NULL);
  return p;
}

gpointer xrealloc (gpointer p, gsize size)
{
  p = realloc (p, size);
  if (size)
    assert (p != NULL);
  return p;
}
#endif

/* Initialise some engine settings from saved preferences */
static gboolean post_init_configure (void)
{
  xine_cfg_entry_t entry;

  if (xine_config_lookup_entry (xine, "gui.post_plugins.deinterlace", &entry))
    gtk_video_set_post_plugins_deinterlace ((GtkVideo *)gtv, entry.str_value);

  if (xine_config_lookup_entry (xine, "gui.post_plugins.video", &entry))
    gtk_video_set_post_plugins_video ((GtkVideo *)gtv, entry.str_value);

  if (xine_config_lookup_entry (xine, "gui.post_plugins.audio", &entry))
    gtk_video_set_post_plugins_audio ((GtkVideo *)gtv, entry.str_value, audio_port);

  if (xine_config_lookup_entry (xine, "gui.post_plugins.deinterlace_enable",
				&entry))
    gtk_video_set_use_post_plugins_deinterlace ((GtkVideo *)gtv, entry.num_value);

  if (xine_config_lookup_entry (xine, "gui.post_plugins.video_enable", &entry))
    gtk_video_set_use_post_plugins_video ((GtkVideo *)gtv, entry.num_value);

  if (xine_config_lookup_entry (xine, "gui.post_plugins.audio_enable", &entry))
    gtk_video_set_use_post_plugins_audio ((GtkVideo *)gtv, entry.num_value, audio_port);

  if (xine_config_lookup_entry (xine, "gui.post_plugins.audio_visualisation",
				&entry))
    vis_set (entry.enum_values[entry.num_value]);

  if (xine_config_lookup_entry (xine, "gui.windowedmode_unblank", &entry))
    gtk_video_set_windowed_unblank ((GtkVideo *)gtv, entry.num_value);

  return FALSE;
}

static gboolean main_start_play (gpointer data)
{
  playlist_play (*(int *)data);
  return FALSE;
}

static gboolean set_fullscreen (gpointer data)
{
  engine_exec ("vo_fullscreen.v = true;", NULL, NULL, NULL);
  return FALSE;
}

#ifdef WITH_WATCHDOG
/* Watchdog functions */

static gboolean watchdog_timer_reset (gpointer data)
{
  alarm (30);
  return TRUE;
}

static void watchdog_timeout (int sig)
{
  /* if this happens, we have a problem... */
  g_printerr (_("gxine: killed by watchdog bite\n"));
  abort ();
}
#endif

/* Our own locking for GDK */

static GStaticRecMutex gdk_lock = G_STATIC_REC_MUTEX_INIT;

static void gxine_gdk_lock (void)
{
  g_static_rec_mutex_lock (&gdk_lock);
}

static void gxine_gdk_unlock (void)
{
  g_static_rec_mutex_unlock (&gdk_lock);
}

/* Faults */

static void fault_handler (int sig)
{
  static gboolean recurse = FALSE;
  if (recurse)
    abort ();

  signal (sig, fault_handler);
  recurse = TRUE;

  const char *sigtext = strsignal (sig);
  fputs (_("gxine has suffered a fatal internal error.\n"
	   "To get a backtrace, run gxine in a debugger such as gdb.\n"
	   "Then, when the error occurs:\n"
	   "  (gdb) thread apply all bt\n"), stderr);
  display_error_modal (FROM_GXINE, _("Fatal error"), "%s",
		       sigtext ? : _("Unknown"));
  exit (2);
}

/* Main */

static char *
getdir (const char *env, const char *dir)
{
  return getenv (env) ? : g_strconcat (dir, NULL);
}

int main(int argc, char* argv[])
{
  gboolean enqueue, autoplay, fullscreen;
  gboolean no_connect;
  int optstate;
  int show_splash = 2; /* default */
  const char *cmd = NULL;
  char *geometry = NULL;

#ifndef GLIB_USES_SYSTEM_MALLOC
  GMemVTable vtab = { xmalloc, xrealloc, free, NULL, NULL, NULL };
  g_mem_set_vtable (&vtab);
#endif

  setlinebuf (stdout);
  setlinebuf (stderr);

  /*
   * init paths here. defaults are compiled in and may
   * be overwritten by environment variables
   */

  plugindir = getdir ("GXINE_PLUGINDIR", GXINE_PLUGINDIR);
  bindir    = getdir ("GXINE_BINDIR", GXINE_BINDIR);
  logodir   = getdir ("GXINE_LOGODIR", GXINE_LOGODIR);
  pixmapdir = getdir ("GXINE_PIXMAPDIR", GXINE_PIXMAPDIR);
  icondir   = getdir ("GXINE_ICONDIR", GXINE_ICONDIR);
  miscdir   = getdir ("GXINE_MISCDIR", GXINE_MISCDIR);
  confdir   = getdir ("GXINE_CONFDIR", GXINE_CONFDIR);

  /* set up our gtkrc stuff */
  {
    gchar **file = gtk_rc_get_default_files ();
    const gchar *home = g_get_home_dir ();
    guint homelen = strlen (home);
    guint i, length = g_strv_length (file);
    /* deep-copy the gtkrc filenames, allocating space for two more */
    gchar **copy = calloc (length + 3, sizeof (gchar *));
    for (i = 0; file[i]; ++i)
      copy[i] = strdup (file[i]);
    /* find the index of the first *user* gtkrc filename */
    for (i = 0; file[i]; ++i)
      if (!strncmp (file[i], home, homelen) && file[i][homelen] == '/')
	break;
    /* insert our system gtkrc filename before it (strdup for convenience) */
    memmove (copy + i + 1, copy + i, (length - i) * sizeof (gchar *));
    copy[i] = g_build_filename (confdir, FILE_GTKRC, NULL);
    /* now append our user gtkrc, terminate the list and tell GTK+ about it */
    copy[length + 1] = get_config_filename (FILE_GTKRC);
    copy[length + 2] = NULL;
    gtk_rc_set_default_files (copy);
    /* free the list */
    g_strfreev (copy);
  }

  /*
   * init glib/gdk/gtk thread safe/aware
   */

#ifdef ENABLE_NLS
  setlocale (LC_ALL, "");
  bindtextdomain (PACKAGE, LOCALEDIR);
  bindtextdomain (PACKAGE".theme", LOCALEDIR);
  /* The next two lines prevent GTK errors caused by gettext's conversion of
   * text from UTF-8.
   */
  bind_textdomain_codeset (PACKAGE, "UTF-8");
  bind_textdomain_codeset (PACKAGE".theme", "UTF-8");
  bind_textdomain_codeset (LIB_PACKAGE, "UTF-8");
  textdomain (PACKAGE);
#endif

  g_thread_init (NULL);
  console_output_init ();
  gdk_threads_set_lock_functions (gxine_gdk_lock, gxine_gdk_unlock);
  gdk_threads_init ();

  if (!gtk_parse_args (&argc, &argv))
    return 2;

  /*
   * parse command line arguments
   */

  verbosity = 0;
  enqueue = FALSE;
  autoplay = TRUE;
  fullscreen = FALSE;
  no_connect = FALSE;
  optstate = 0;
  for (;;)
  {
#define OPTS "hvaefV:A:sSc:n"
#ifdef HAVE_GETOPT_LONG
    static struct option longopts[] = {
      { "help", no_argument, NULL, 'h' },
      { "version", no_argument, NULL, 1 },
      { "verbose", no_argument, NULL, 'v' },
      { "add", no_argument, NULL, 'a' },
      { "enqueue", no_argument, NULL, 'e' },
      { "full-screen", no_argument, NULL, 'f' },
      { "video", required_argument, NULL, 'V' },
      { "audio", required_argument, NULL, 'A' },
      { "splash", no_argument, NULL, 's' },
      { "no-splash", no_argument, NULL, 'S' },
      { "command", required_argument, NULL, 'c' },
      { "geometry", required_argument, NULL, 2 },
      { NULL }
    };
    int index = 0;
    int opt = getopt_long (argc, argv, OPTS, longopts, &index);
#else
    int opt = getopt(argc, argv, OPTS);
#endif
    if (opt == -1)
      break;

    switch (opt)
    {
    case 'h':
      optstate |= 1;
      break;
    case 1:
      optstate |= 4;
      break;
    case 2:
      geometry = optarg;
      break;
    case 'v':
      verbosity++;
      break;
    case 'a':
      autoplay = TRUE;
      enqueue = TRUE;
      break;
    case 'e':
      autoplay = FALSE;
      enqueue = TRUE;
      break;
    case 'f':
      fullscreen = TRUE;
      break;
    case 'V':
      video_driver_id = optarg;
      break;
    case 'A':
      audio_driver_id = optarg;
      break;
    case 's':
      show_splash = TRUE;
      break;
    case 'S':
      show_splash = FALSE;
      break;
    case 'c':
      cmd = optarg;
      break;
    case 'n':
      no_connect = TRUE;
      break;
    default:
      optstate |= 2;
      break;
    }
  }

  if (optstate & 1)
    g_print (_("\
gxine %s (%s)\n\
usage: %s [options] [MRLs...]\n\
options:\n\
  -h, --help		this help text\n\
  -A, --audio DRIVER	try to use this audio driver\n\
  -V, --video DRIVER	try to use this video driver\n\
  -S, --no-splash	don't show the splash window\n\
  -c, --command TEXT	Javascript command(s) to be executed at startup\n\
  -a, --add		don't clear the playlist, play the first new item\n\
  -e, --enqueue		don't clear the playlist, don't play the new items\n\
  -f, --full-screen	start in full-screen mode\n\
  -v, --verbose		be more verbose\n\
\n"), VERSION, VENDOR_PKG_VERSION CSET_ID, argv[0]);
  else if (optstate & 4)
    g_print ("gxine %s (%s)\n%s\n%s", VERSION, VENDOR_PKG_VERSION CSET_ID,
	     get_copyright_notice (), _("\
This is free software; see the source for copying conditions.  There is NO\n\
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE,\n\
to the extent permitted by law.\n"));

  if (optstate & 2)
  {
    g_printerr (_("gxine: invalid option (try -h or --help)\n"));
    return 1;
  }

  if (optstate)
    return 0;

  /* init gtk windowing */

  gtk_init(&argc, &argv);
  gtk_window_set_auto_startup_notification (FALSE);

  signal (SIGSEGV, fault_handler);

  /*
   * find out if gxine is already running, if so
   * just pass on the files to play
   */

  sched_yield ();
  if (!no_connect)
    gxine_try_remote (argc, argv, enqueue, autoplay);

  /* start using X... */
  if (!XInitThreads ())
  {
    g_printerr (_("gtkvideo: XInitThreads failed - looks like you don't have a thread-safe xlib.\n"));
    return 2;
  }

  /*
   * make sure ~/.gxine exists
   */
  {
    char *fname = get_config_filename (NULL);
    int r = ensure_path_exists (fname, 0700);
    if (r)
      display_warning
	(FROM_GXINE, _("File creation error"),
	 _("Couldn't create ~/.gxine: %s.\n"
	   "Configuration, playlist and media marks will not be saved."),
	 strerror (r));
    g_free (fname);
  }

#ifdef WITH_WATCHDOG
  /*
   * Watchdog
   */

  g_timeout_add (2500, watchdog_timer_reset, NULL); /* <- will set the alarm */
  signal (SIGALRM, watchdog_timeout);
#endif

  /*
   * init xine, set up script engine
   */

  engine_init ();
  server_setup ();

  /*
   * a splash screen for the impatient
   */

  if (show_splash == 2)
  {
    xine_cfg_entry_t entry;
    show_splash = !xine_config_lookup_entry (xine, "gui.show_splash", &entry)
		  || entry.num_value;
  }

  if (show_splash)
    splash_show ();

  gdk_threads_enter ();
  do_pending_events ();

  /*
   * set up player and some UI basics
   */

  player_init ();
  ui_init ();

  gtk_window_set_default_icon_name (GXINE_LOGO);
  

  {
    char *fname = get_config_filename (FILE_ACCELS);
    gtk_accel_map_load (fname);
    free (fname);
  }

  do_pending_events ();
  gtk_window_set_auto_startup_notification (TRUE);
  noskin_main_init (geometry);

  do_pending_events ();

  /*
   * create all dialogue boxes etc.
   */

  file_dialog_init ();
  utils_init       ();
  open_mrl_init    ();
  log_window_init  ();
  preferences_init ();
  bowdlerpref_init ();
  playlist_init    ();
  settings_init    ();
  mediamarks_init  ();
  play_item_init   ();
  key_events_init  ();
  gxine_lirc_init  ();
  snapshot_init    ();
  vis_init	   ();
  post_init	   ();
  stream_info_init ();
  wizards_init     ();

  engine_startup_script ();

  /*
   * wizards (first run only)
   */

  run_wizards (FALSE);
  gdk_threads_leave ();

  post_init_configure ();
  initialised = TRUE;

  /*
   * argument parsing
   */

  if (optind < argc)
  {
    int i;
    static int first = -1;

    if (!enqueue)
      playlist_clear();

    for (i = optind; i < argc; ++i)
      if (first < 0)
	first = playlist_add_mrl (argv[i], -1);
      else
	playlist_add_mrl (argv[i], -1);

    if (first < 0)
      gtk_init_add ((GtkFunction) playlist_logo, NULL);
    else
      gtk_init_add (main_start_play,  &first);
  }
  else
    gtk_init_add ((GtkFunction) playlist_logo, NULL);

  if (fullscreen)
    gtk_init_add (set_fullscreen, NULL);

  gtk_init_add ((GtkFunction) ui_post_init, NULL);
  server_start ();

  /* execute any command-line JS */
  if (cmd)
    se_eval (gse, cmd, NULL, NULL, NULL, "--command");

  //gdk_threads_enter();
  gtk_main();
  //gdk_threads_leave();

  return 0;
}
